APIClient.transactions   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
c 0
b 0
f 0
nc 2
dl 0
loc 19
rs 9.4285
nop 2

1 Function

Rating   Name   Duplication   Size   Complexity  
A 0 3 1
1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    BtccomConverter = require('./btccom.convert'),
11
    BlocktrailConverter = require('./blocktrail.convert'),
12
    RestClient = require('./rest_client'),
13
    Encryption = require('./encryption'),
14
    KeyDerivation = require('./keyderivation'),
15
    EncryptionMnemonic = require('./encryption_mnemonic'),
16
    blocktrail = require('./blocktrail'),
17
    randomBytes = require('randombytes'),
18
    CryptoJS = require('crypto-js'),
19
    webworkifier = require('./webworkifier');
20
21
/**
22
 *
23
 * @param opt
24
 * @returns {*}
25
 */
26
function networkFromOptions(opt) {
27
    if (opt.bitcoinCash) {
28
        if (opt.regtest) {
29
            return bitcoin.networks.bitcoincashregtest;
30
        } else if (opt.testnet) {
31
            return bitcoin.networks.bitcoincashtestnet;
32
        } else {
33
            return bitcoin.networks.bitcoincash;
34
        }
35
    } else {
36
        if (opt.regtest) {
37
            return bitcoin.networks.regtest;
38
        } else if (opt.testnet) {
39
            return bitcoin.networks.testnet;
40
        } else {
41
            return bitcoin.networks.bitcoin;
42
        }
43
    }
44
}
45
46
var useWebWorker = require('./use-webworker')();
47
48
49
/**
50
 * helper to wrap a promise so that the callback get's called when it succeeds or fails
51
 *
52
 * @param promise   {q.Promise}
53
 * @param cb        function
54
 * @return q.Promise
55
 */
56
function callbackify(promise, cb) {
57
    // add a .then to trigger the cb for people using callbacks
58
    if (cb) {
59
        promise
60
            .then(function(res) {
61
                // use q.nextTick for asyncness
62
                q.nextTick(function() {
63
                    cb(null, res);
64
                });
65
            }, function(err) {
66
                // use q.nextTick for asyncness
67
                q.nextTick(function() {
68
                    cb(err, null);
69
                });
70
            });
71
    }
72
73
    // return the promise for people using promises
74
    return promise;
75
}
76
77
/**
78
 * Bindings to consume the BlockTrail API
79
 *
80
 * @param options       object{
81
 *                          apiKey: 'API_KEY',
82
 *                          apiSecret: 'API_SECRET',
83
 *                          host: 'defaults to api.blocktrail.com',
84
 *                          network: 'BTC|LTC',
85
 *                          testnet: true|false
86
 *                      }
87
 * @constructor
88
 */
89
var APIClient = function(options) {
90
    var self = this;
91
92
    // handle constructor call without 'new'
93
    if (!(this instanceof APIClient)) {
94
        return new APIClient(options);
95
    }
96
97
    var normalizedNetwork = APIClient.normalizeNetworkFromOptions(options);
98
    options.network = normalizedNetwork[0];
99
    options.testnet = normalizedNetwork[1];
100
    options.regtest = normalizedNetwork[2];
101
    // apiNetwork we allow to be customized for debugging purposes
102
    options.apiNetwork = options.apiNetwork || normalizedNetwork[3];
103
104
    self.bitcoinCash = options.network === "BCC";
105
    self.regtest = options.regtest;
106
    self.testnet = options.testnet;
107
    self.network = networkFromOptions(self);
108
    self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true;
109
    self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200;
110
111
    options.apiNetwork = options.apiNetwork || ((self.testnet ? "t" : "") + (options.network || 'BTC').toUpperCase());
112
113
    if (typeof options.btccom === "undefined") {
114
        options.btccom = true;
115
    }
116
117
    /**
118
     * @type RestClient
119
     */
120
    var dataOptions = _.omit(options, 'host');
121
    self.dataClient = APIClient.initRestClient(dataOptions);
122
    /**
123
     * @type RestClient
124
     */
125
    self.blocktrailClient = APIClient.initRestClient(_.merge({}, options, {btccom: false}));
126
127
    if (options.btccom) {
128
        self.converter = new BtccomConverter(self.network, true);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
129
    } else {
130
        self.converter = new BlocktrailConverter();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
131
    }
132
133
};
134
135
APIClient.normalizeNetworkFromOptions = function(options) {
136
    /* jshint -W071, -W074 */
137
    var network = 'BTC';
138
    var testnet = false;
139
    var regtest = false;
140
    var apiNetwork = "BTC";
0 ignored issues
show
Unused Code introduced by
The assignment to variable apiNetwork seems to be never used. Consider removing it.
Loading history...
141
142
    var prefix;
143
    var done = false;
144
145
    if (options.network) {
146
        var lower = options.network.toLowerCase();
147
148
        var m = lower.match(/^([rt])?(btc|bch|bcc)$/);
149
        if (!m) {
150
            throw new Error("Invalid network [" + options.network + "]");
151
        }
152
153
        if (m[2] === 'btc') {
154
            network = "BTC";
155
        } else {
156
            network = "BCC";
157
        }
158
159
        prefix = m[1];
160
        if (prefix) {
161
            // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after
162
            done = true;
163
            if (prefix === 'r') {
164
                testnet = true;
165
                regtest = true;
166
            } else if (prefix === 't') {
167
                testnet = true;
168
            }
169
        }
170
    }
171
172
    // if we're not already done then apply options.regtest and options.testnet
173
    if (!done) {
174
        if (options.regtest) {
175
            testnet = true;
176
            regtest = true;
177
            prefix = "r";
178
        } else if (options.testnet) {
179
            testnet = true;
180
            prefix = "t";
181
        }
182
    }
183
184
    apiNetwork = (prefix || "") + network;
185
186
    return [network, testnet, regtest, apiNetwork];
187
};
188
189
APIClient.updateHostOptions = function(options) {
190
    /* jshint -W071, -W074 */
191
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
192
    if (!options.btccom && process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
193
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
194
    }
195
    if (options.btccom && process.env.BLOCKTRAIL_SDK_BTCCOM_API_ENDPOINT) {
196
        options.host = process.env.BLOCKTRAIL_SDK_BTCCOM_API_ENDPOINT;
197
    }
198
199
    if (options.btccom && process.env.BLOCKTRAIL_SDK_THROTTLE_BTCCOM) {
200
        options.throttleRequestsTimeout = process.env.BLOCKTRAIL_SDK_THROTTLE_BTCCOM;
201
    }
202
203
    if (options.btccom) {
204
        if (!options.host) {
205
            options.host = options.btccomhost || (options.network === 'BCC' ? 'bch-chain.api.btc.com' : 'chain.api.btc.com');
206
        }
207
208
        if (options.testnet && !options.host.match(/tchain/)) {
209
            options.host = options.host.replace(/chain/, 'tchain');
210
        }
211
212
        if (!options.endpoint) {
213
            options.endpoint = options.btccomendpoint || ("/" + (options.apiVersion || "v3"));
214
        }
215
    } else {
216
        if (!options.host) {
217
            options.host = 'api.blocktrail.com';
218
        }
219
220
        if (!options.endpoint) {
221
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
222
        }
223
    }
224
225
    // trim off leading https?://
226
    if (options.host && options.host.indexOf("https://") === 0) {
227
        options.https = true;
228
        options.host = options.host.substr(8);
229
    } else if (options.host && options.host.indexOf("http://") === 0) {
230
        options.https = false;
231
        options.host = options.host.substr(7);
232
    }
233
234
    if (typeof options.https === "undefined") {
235
        options.https = true;
236
    }
237
238
    if (!options.port) {
239
        options.port = options.https ? 443 : 80;
240
    }
241
242
    return options;
243
};
244
245
APIClient.initRestClient = function(options) {
246
    options = APIClient.updateHostOptions(options);
247
    return new RestClient(options);
248
};
249
250
var determineDataStorageV2_3 = function(options) {
251
    return q.when(options)
252
        .then(function(options) {
253
            // legacy
254
            if (options.storePrimaryMnemonic) {
255
                options.storeDataOnServer = options.storePrimaryMnemonic;
256
            }
257
258
            // storeDataOnServer=false when primarySeed is provided
259
            if (typeof options.storeDataOnServer === "undefined") {
260
                options.storeDataOnServer = !options.primarySeed;
261
            }
262
263
            return options;
264
        });
265
};
266
267
var produceEncryptedDataV2 = function(options, notify) {
268
    return q.when(options)
269
        .then(function(options) {
270
            if (options.storeDataOnServer) {
271
                if (!options.secret) {
272
                    if (!options.passphrase) {
273
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
274
                    }
275
276
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
277
278
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
279
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
280
                }
281
282
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
283
284
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
285
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
286
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
287
288
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
289
290
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
291
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
292
            }
293
294
            return options;
295
        });
296
};
297
298
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
299
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
300
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
301
        var saltBuf = Encryption.generateSalt();
302
        var iv = Encryption.generateIV();
303
304
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
305
            return require('./webworker');
306
        }, onLoadWorkerLoadAsmCrypto, {
307
            method: 'Encryption.encryptWithSaltAndIV',
308
            pt: pt,
309
            pw: pw,
310
            saltBuf: saltBuf,
311
            iv: iv,
312
            iterations: iter
313
        })
314
            .then(function(data) {
315
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
316
            });
317
    } else {
318
        try {
319
            return q.when(Encryption.encrypt(pt, pw, iter));
320
        } catch (e) {
321
            return q.reject(e);
322
        }
323
    }
324
};
325
326
APIClient.prototype.promisedDecrypt = function(ct, pw) {
327
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
328
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
329
            return require('./webworker');
330
        }, onLoadWorkerLoadAsmCrypto, {
331
            method: 'Encryption.decrypt',
332
            ct: ct,
333
            pw: pw
334
        })
335
            .then(function(data) {
336
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
337
            });
338
    } else {
339
        try {
340
            return q.when(Encryption.decrypt(ct, pw));
341
        } catch (e) {
342
            return q.reject(e);
343
        }
344
    }
345
};
346
347
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
348
    var self = this;
349
350
    return q.when(options)
351
        .then(function(options) {
352
            if (options.storeDataOnServer) {
353
                return q.when()
354
                    .then(function() {
355
                        if (!options.secret) {
356
                            if (!options.passphrase) {
357
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
358
                            }
359
360
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
361
362
                            // -> now a buffer
363
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
364
365
                            // -> now a buffer
366
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
367
                                .then(function(encryptedSecret) {
368
                                    options.encryptedSecret = encryptedSecret;
369
                                });
370
                        } else {
371
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
372
                                throw new Error('Secret must be a buffer');
373
                            }
374
                        }
375
                    })
376
                    .then(function() {
377
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
378
379
                        return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations)
380
                            .then(function(encryptedPrimarySeed) {
381
                                options.encryptedPrimarySeed = encryptedPrimarySeed;
382
                            });
383
                    })
384
                    .then(function() {
385
                        // skip generating recovery secret when explicitly set to false
386
                        if (options.recoverySecret === false) {
387
                            return;
388
                        }
389
390
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
391
                        if (!options.recoverySecret) {
392
                            options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
393
                        }
394
395
                        return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations)
396
                            .then(function(recoveryEncryptedSecret) {
397
                                options.recoveryEncryptedSecret = recoveryEncryptedSecret;
398
                            });
399
                    })
400
                    .then(function() {
401
                        return options;
402
                    });
403
            } else {
404
                return options;
405
            }
406
        });
407
};
408
409
var doRemainingWalletDataV2_3 = function(options, network, notify) {
410
    return q.when(options)
411
        .then(function(options) {
412
            if (!options.backupPublicKey) {
413
                options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
414
            }
415
416
            notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
417
418
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network);
419
420
            notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
421
422
            if (!options.backupPublicKey) {
423
                options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network);
424
                options.backupPublicKey = options.backupPrivateKey.neutered();
425
            }
426
427
            options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered();
428
429
            notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
430
431
            return options;
432
        });
433
};
434
435
APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) {
436
    var self = this;
437
438
    var deferred = q.defer();
439
    deferred.promise.spreadNodeify(cb);
440
441
    deferred.resolve(q.fcall(function() {
442
        return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) {
443
            return bitcoin.HDNode.fromSeedHex(seedHex, self.network);
444
        });
445
    }));
446
447
    return deferred.promise;
448
};
449
450
APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) {
451
    var self = this;
452
453
    if (useWebWorker) {
454
        return webworkifier.workify(self.mnemonicToSeedHex, function() {
455
            return require('./webworker');
456
        }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase})
457
            .then(function(data) {
458
                return data.seed;
459
            });
460
    } else {
461
        try {
462
            return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase));
463
        } catch (e) {
464
            return q.reject(e);
465
        }
466
    }
467
};
468
469
APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) {
470
    var self = this;
471
472
    var deferred = q.defer();
473
    deferred.promise.nodeify(cb);
474
475
    try {
476
        // avoid conflicting options
477
        if (options.passphrase && options.password) {
478
            throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
479
        }
480
        // normalize passphrase/password
481
        options.passphrase = options.passphrase || options.password;
482
        delete options.password;
483
484
        // avoid conflicting options
485
        if (options.primaryMnemonic && options.primarySeed) {
486
            throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed");
487
        }
488
489
        // avoid deprecated options
490
        if (options.primaryPrivateKey) {
491
            throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
492
        }
493
494
        // make sure we have at least one thing to use
495
        if (!options.primaryMnemonic && !options.primarySeed) {
496
            throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed");
497
        }
498
499
        if (options.primarySeed) {
500
            self.primarySeed = options.primarySeed;
501
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
502
            deferred.resolve(options);
503
        } else {
504
            if (!options.passphrase) {
505
                throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase");
506
            }
507
508
            self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase)
509
                .then(function(seedHex) {
510
                    try {
511
                        options.primarySeed = new Buffer(seedHex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
512
                        options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, self.network);
513
                        deferred.resolve(options);
514
                    } catch (e) {
515
                        deferred.reject(e);
516
                    }
517
                }, function(e) {
518
                    deferred.reject(e);
519
                });
520
        }
521
    } catch (e) {
522
        deferred.reject(e);
523
    }
524
525
    return deferred.promise;
526
};
527
528
APIClient.prototype.resolveBackupPublicKeyFromOptions = function(options, cb) {
529
    var self = this;
530
531
    var deferred = q.defer();
532
    deferred.promise.nodeify(cb);
533
534
    try {
535
        // avoid conflicting options
536
        if (options.backupMnemonic && options.backupPublicKey) {
537
            throw new blocktrail.WalletInitError("Can only specify one of; Backup Mnemonic or Backup PublicKey");
538
        }
539
540
        // make sure we have at least one thing to use
541
        if (!options.backupMnemonic && !options.backupPublicKey) {
542
            throw new blocktrail.WalletInitError("Need to specify at least one of; Backup Mnemonic or Backup PublicKey");
543
        }
544
545
        if (options.backupPublicKey) {
546
            if (options.backupPublicKey instanceof bitcoin.HDNode) {
547
                deferred.resolve(options);
548
            } else {
549
                options.backupPublicKey = bitcoin.HDNode.fromBase58(options.backupPublicKey, self.network);
550
                deferred.resolve(options);
551
            }
552
        } else {
553
            self.mnemonicToPrivateKey(options.backupMnemonic, "").then(function(backupPrivateKey) {
554
                options.backupPublicKey = backupPrivateKey.neutered();
555
                deferred.resolve(options);
556
            }, function(e) {
557
                deferred.reject(e);
558
            });
559
        }
560
    } catch (e) {
561
        deferred.reject(e);
562
    }
563
564
    return deferred.promise;
565
};
566
567
APIClient.prototype.debugAuth = function(cb) {
568
    var self = this;
569
570
    return self.dataClient.get("/debug/http-signature", null, true, cb);
571
};
572
573
/**
574
 * get a single address
575
 *
576
 * @param address      string       address hash
577
 * @param [cb]          function    callback function to call when request is complete
578
 * @return q.Promise
579
 */
580
APIClient.prototype.address = function(address, cb) {
581
    var self = this;
582
583
    return callbackify(self.dataClient.get(self.converter.getUrlForAddress(address), null)
584
        .then(function(data) {
585
            return self.converter.handleErros(self, data);
586
        })
587
        .then(function(data) {
588
            if (data === null) {
589
                return data;
590
            } else {
591
                return self.converter.convertAddress(data);
592
            }
593
        }), cb);
594
};
595
596
APIClient.prototype.addresses = function(addresses, cb) {
597
    var self = this;
598
599
    return callbackify(self.dataClient.post("/address", null, {"addresses": addresses}), cb);
600
};
601
602
603
/**
604
 * get all transactions for an address (paginated)
605
 *
606
 * @param address       string      address hash
607
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
608
 * @param [cb]          function    callback function to call when request is complete
609
 * @return q.Promise
610
 */
611
APIClient.prototype.addressTransactions = function(address, params, cb) {
612
613
    var self = this;
614
615
    if (typeof params === "function") {
616
        cb = params;
617
        params = null;
618
    }
619
620
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
621
        .then(function(data) {
622
            return self.converter.handleErros(self, data);
623
        })
624
        .then(function(data) {
625
            return data.data === null ? data : self.converter.convertAddressTxs(data);
626
        }), cb);
627
};
628
629
/**
630
 * get all transactions for a batch of addresses (paginated)
631
 *
632
 * @param addresses     array       address hashes
633
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
634
 * @param [cb]          function    callback function to call when request is complete
635
 * @return q.Promise
636
 */
637
APIClient.prototype.batchAddressHasTransactions = function(addresses, params, cb) {
638
    var self = this;
639
640
    if (typeof params === "function") {
641
        cb = params;
642
        params = null;
643
    }
644
645
    var deferred = q.defer();
646
647
    var promise = q();
648
649
    addresses.forEach(function(address) {
650
        promise = promise.then(function(hasTxs) {
651
            if (hasTxs) {
652
                return hasTxs;
653
            }
654
655
            return q(address)
656
                .then(function(address) {
657
                    console.log(address);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
658
                    return self.addressTransactions(address, params)
659
                        .then(function(res) {
660
                            // err_no=1 is no txs found
661
                            if (res.err_no === 1) {
662
                                return false;
663
                            } else if (res.err_no) {
664
                                throw new Error("err: " + res.err_msg);
665
                            }
666
667
                            return res.data && res.data.length > 0;
668
                        });
669
                });
670
        });
671
    });
672
673
    promise.then(function(hasTxs) {
674
        deferred.resolve({has_transactions: hasTxs});
675
    }, function(err) {
676
        deferred.reject(err);
677
    });
678
679
    return callbackify(deferred.promise, cb);
680
};
681
682
/**
683
 * get all unconfirmed transactions for an address (paginated)
684
 *
685
 * @param address       string      address hash
686
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
687
 * @param [cb]          function    callback function to call when request is complete
688
 * @return q.Promise
689
 */
690
APIClient.prototype.addressUnconfirmedTransactions = function(address, params, cb) {
691
    var self = this;
692
693
    if (typeof params === "function") {
694
        cb = params;
695
        params = null;
696
    }
697
698
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
699
        .then(function(data) {
700
            return self.converter.handleErros(self, data);
701
        })
702
        .then(function(data) {
703
            if (data.data === null) {
704
                return data;
705
            }
706
707
            var res = self.converter.convertAddressTxs(data);
708
            res.data = res.data.filter(function(tx) {
709
                return !tx.confirmations;
710
            });
711
712
            return res;
713
        }), cb);
714
};
715
716
/**
717
 * get all unspent outputs for an address (paginated)
718
 *
719
 * @param address       string      address hash
720
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
721
 * @param [cb]          function    callback function to call when request is complete
722
 * @return q.Promise
723
 */
724
APIClient.prototype.addressUnspentOutputs = function(address, params, cb) {
725
    var self = this;
726
727
    if (typeof params === "function") {
728
        cb = params;
729
        params = null;
730
    }
731
732
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressUnspent(address), self.converter.paginationParams(params))
733
        .then(function(data) {
734
            return self.converter.handleErros(self, data);
735
        })
736
        .then(function(data) {
737
            return data.data === null ? data : self.converter.convertAddressUnspentOutputs(data, address);
738
        }), cb);
739
};
740
741
/**
742
 * get all unspent outputs for a batch of addresses (paginated)
743
 *
744
 * @param addresses     array       address hashes
745
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
746
 * @param [cb]          function    callback function to call when request is complete
747
 * @return q.Promise
748
 */
749
APIClient.prototype.batchAddressUnspentOutputs = function(addresses, params, cb) {
750
    var self = this;
751
752
    if (self.converter instanceof BtccomConverter) {
753
        return callbackify(self.dataClient.get(self.converter.getUrlForBatchAddressUnspent(addresses), self.converter.paginationParams(params))
754
            .then(function(data) {
755
                return self.converter.handleErros(self, data);
756
            })
757
            .then(function(data) {
758
                return data.data === null ? data : self.converter.convertBatchAddressUnspentOutputs(data);
759
            }), cb);
760
    } else {
761
762
        if (typeof params === "function") {
763
            cb = params;
764
            params = null;
765
        }
766
767
        return callbackify(self.dataClient.post("/address/unspent-outputs", params, {"addresses": addresses}), cb);
768
    }
769
};
770
771
/**
772
 * verify ownership of an address
773
 *
774
 * @param address       string      address hash
775
 * @param signature     string      a signed message (the address hash) using the private key of the address
776
 * @param [cb]          function    callback function to call when request is complete
777
 * @return q.Promise
778
 */
779
APIClient.prototype.verifyAddress = function(address, signature, cb) {
780
    var self = this;
781
782
    return self.verifyMessage(address, address, signature, cb);
783
};
784
785
/**
786
 *
787
 * get all blocks (paginated)
788
 * ASK
789
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
790
 * @param [cb]          function    callback function to call when request is complete
791
 * @return q.Promise
792
 */
793
APIClient.prototype.allBlocks = function(params, cb) {
794
    var self = this;
795
796
    if (typeof params === "function") {
797
        cb = params;
798
        params = null;
799
    }
800
801
    return callbackify(self.dataClient.get(self.converter.getUrlForAllBlocks(), self.converter.paginationParams(params))
802
            .then(function(data) {
803
                return self.converter.handleErros(self, data);
804
            })
805
            .then(function(data) {
806
                return data.data === null ? data : self.converter.convertBlocks(data);
807
            }), cb);
808
};
809
810
/**
811
 * get a block
812
 *
813
 * @param block         string|int  a block hash or a block height
814
 * @param [cb]          function    callback function to call when request is complete
815
 * @return q.Promise
816
 */
817
APIClient.prototype.block = function(block, cb) {
818
    var self = this;
819
820
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock(block), null)
821
        .then(function(data) {
822
            return self.converter.handleErros(self, data);
823
        })
824
        .then(function(data) {
825
            return data.data === null ? data : self.converter.convertBlock(data.data);
826
        }), cb);
827
};
828
829
/**
830
 * get the latest block
831
 *
832
 * @param [cb]          function    callback function to call when request is complete
833
 * @return q.Promise
834
 */
835
APIClient.prototype.blockLatest = function(cb) {
836
    var self = this;
837
838
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock("latest"), null)
839
        .then(function(data) {
840
            return self.converter.handleErros(self, data);
841
        })
842
        .then(function(data) {
843
            return data.data === null ? data : self.converter.convertBlock(data.data);
844
        }), cb);
845
};
846
847
/**
848
 * get all transactions for a block (paginated)
849
 *
850
 * @param block         string|int  a block hash or a block height
851
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
852
 * @param [cb]          function    callback function to call when request is complete
853
 * @return q.Promise
854
 */
855
APIClient.prototype.blockTransactions = function(block, params, cb) {
856
    var self = this;
857
858
    if (typeof params === "function") {
859
        cb = params;
860
        params = null;
861
    }
862
863
    return callbackify(self.dataClient.get(self.converter.getUrlForBlockTransaction(block), self.converter.paginationParams(params))
864
        .then(function(data) {
865
            return self.converter.handleErros(self, data);
866
        })
867
        .then(function(data) {
868
            return data.data ===  null ? data : self.converter.convertBlockTxs(data);
869
        }), cb);
870
};
871
872
/**
873
 * get a single transaction
874
 *
875
 * @param tx            string      transaction hash
876
 * @param [cb]          function    callback function to call when request is complete
877
 * @return q.Promise
878
 */
879
APIClient.prototype.transaction = function(tx, cb) {
880
    var self = this;
881
882
    return callbackify(self.dataClient.get(self.converter.getUrlForTransaction(tx), null)
883
        .then(function(data) {
884
            return self.converter.handleErros(self, data);
885
        })
886
        .then(function(data) {
887
            if (data.data === null) {
888
                return data;
889
            } else {
890
                // for BTC.com API we need to fetch the raw hex from the BTC.com explorer endpoint
891
                if (self.converter instanceof BtccomConverter) {
892
                    return self.dataClient.get(self.converter.getUrlForRawTransaction(tx), null)
893
                        .then(function(rawData) {
894
                            return [data, rawData.data];
895
                        })
896
                        .then(function(dataAndTx) {
897
                            if (dataAndTx !== null) {
898
                                var data = dataAndTx[0];
899
                                var rawTx = dataAndTx[1];
900
                                return self.converter.convertTx(data, rawTx);
901
                            } else {
902
                                return dataAndTx;
903
                            }
904
                        });
905
                } else {
906
                    return self.converter.convertTx(data);
907
                }
908
            }
909
        }), cb);
910
};
911
912
/**
913
 * get a batch of transactions
914
 *
915
 * @param txs           string[]    list of transaction hashes (txId)
916
 * @param [cb]          function    callback function to call when request is complete
917
 * @return q.Promise
918
 */
919
APIClient.prototype.transactions = function(txs, cb) {
920
    var self = this;
921
922
    if (self.converter instanceof BtccomConverter) {
923
        return callbackify(self.dataClient.get(self.converter.getUrlForTransactions(txs), null)
924
            .then(function(data) {
925
                return self.converter.handleErros(self, data);
926
            })
927
            .then(function(data) {
928
                if (data.data === null) {
929
                    return data;
930
                } else {
931
                    return self.converter.convertTxs(data);
932
                }
933
            }), cb);
934
    } else {
935
        return callbackify(self.dataClient.post("/transactions", null, txs, null, false), cb);
936
    }
937
};
938
939
/**
940
 * get a paginated list of all webhooks associated with the api user
941
 *
942
 * @param [params]      object      pagination: {page: 1, limit: 20}
943
 * @param [cb]          function    callback function to call when request is complete
944
 * @return q.Promise
945
 */
946
APIClient.prototype.allWebhooks = function(params, cb) {
947
    var self = this;
948
949
    if (typeof params === "function") {
950
        cb = params;
951
        params = null;
952
    }
953
954
    return self.blocktrailClient.get("/webhooks", params, cb);
955
};
956
957
/**
958
 * create a new webhook
959
 *
960
 * @param url           string      the url to receive the webhook events
961
 * @param [identifier]  string      a unique identifier associated with the webhook
962
 * @param [cb]          function    callback function to call when request is complete
963
 * @return q.Promise
964
 */
965
APIClient.prototype.setupWebhook = function(url, identifier, cb) {
966
    var self = this;
967
968
    if (typeof identifier === "function") {
969
        //mimic function overloading
970
        cb = identifier;
971
        identifier = null;
972
    }
973
974
    return self.blocktrailClient.post("/webhook", null, {url: url, identifier: identifier}, cb);
975
};
976
977
/**
978
 * Converts a cash address to the legacy (base58) format
979
 * @param {string} input
980
 * @returns {string}
981
 */
982
APIClient.prototype.getLegacyBitcoinCashAddress = function(input) {
983
    if (this.network === bitcoin.networks.bitcoincash ||
984
        this.network === bitcoin.networks.bitcoincashtestnet ||
985
        this.network === bitcoin.networks.bitcoincashregtest) {
986
        var address;
987
        try {
988
            bitcoin.address.fromBase58Check(input, this.network);
989
            return input;
990
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
991
992
        address = bitcoin.address.fromCashAddress(input, this.network);
993
        var prefix;
994
        if (address.version === bitcoin.script.types.P2PKH) {
995
            prefix = this.network.pubKeyHash;
996
        } else if (address.version === bitcoin.script.types.P2SH) {
997
            prefix = this.network.scriptHash;
998
        } else {
999
            throw new Error("Unsupported address type");
1000
        }
1001
1002
        return bitcoin.address.toBase58Check(address.hash, prefix);
1003
    }
1004
1005
    throw new Error("Cash addresses only work on bitcoin cash");
1006
};
1007
1008
/**
1009
 * Converts a legacy bitcoin to the new cashaddr format
1010
 * @param {string} input
1011
 * @returns {string}
1012
 */
1013
APIClient.prototype.getCashAddressFromLegacyAddress = function(input) {
1014
    if (this.network === bitcoin.networks.bitcoincash ||
1015
        this.network === bitcoin.networks.bitcoincashtestnet ||
1016
        this.network === bitcoin.networks.bitcoincashregtest
1017
    ) {
1018
        var address;
1019
        try {
1020
            bitcoin.address.fromCashAddress(input, this.network);
1021
            return input;
1022
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
1023
1024
        address = bitcoin.address.fromBase58Check(input, this.network);
1025
        var scriptType;
1026
        if (address.version === this.network.pubKeyHash) {
1027
            scriptType = bitcoin.script.types.P2PKH;
1028
        } else if (address.version === this.network.scriptHash) {
1029
            scriptType = bitcoin.script.types.P2SH;
1030
        } else {
1031
            throw new Error("Unsupported address type");
1032
        }
1033
1034
        return bitcoin.address.toCashAddress(address.hash, scriptType, this.network.cashAddrPrefix);
1035
    }
1036
1037
    throw new Error("Cash addresses only work on bitcoin cash");
1038
};
1039
1040
/**
1041
 * get an existing webhook by it's identifier
1042
 *
1043
 * @param identifier    string      the unique identifier of the webhook to get
1044
 * @param [cb]          function    callback function to call when request is complete
1045
 * @return q.Promise
1046
 */
1047
APIClient.prototype.getWebhook = function(identifier, cb) {
1048
    var self = this;
1049
1050
    return self.blocktrailClient.get("/webhook/" + identifier, null, cb);
1051
};
1052
1053
/**
1054
 * update an existing webhook
1055
 *
1056
 * @param identifier    string      the unique identifier of the webhook
1057
 * @param webhookData   object      the data to update: {identifier: newIdentifier, url:newUrl}
1058
 * @param [cb]          function    callback function to call when request is complete
1059
 * @return q.Promise
1060
 */
1061
APIClient.prototype.updateWebhook = function(identifier, webhookData, cb) {
1062
    var self = this;
1063
1064
    return self.blocktrailClient.put("/webhook/" + identifier, null, webhookData, cb);
1065
};
1066
1067
/**
1068
 * deletes an existing webhook and any event subscriptions associated with it
1069
 *
1070
 * @param identifier    string      the unique identifier of the webhook
1071
 * @param [cb]          function    callback function to call when request is complete
1072
 * @return q.Promise
1073
 */
1074
APIClient.prototype.deleteWebhook = function(identifier, cb) {
1075
    var self = this;
1076
1077
    return self.blocktrailClient.delete("/webhook/" + identifier, null, null, cb);
1078
};
1079
1080
/**
1081
 * get a paginated list of all the events a webhook is subscribed to
1082
 *
1083
 * @param identifier    string      the unique identifier of the webhook
1084
 * @param [params]      object      pagination: {page: 1, limit: 20}
1085
 * @param [cb]          function    callback function to call when request is complete
1086
 * @return q.Promise
1087
 */
1088
APIClient.prototype.getWebhookEvents = function(identifier, params, cb) {
1089
    var self = this;
1090
1091
    if (typeof params === "function") {
1092
        cb = params;
1093
        params = null;
1094
    }
1095
1096
    return self.blocktrailClient.get("/webhook/" + identifier + "/events", params, cb);
1097
};
1098
1099
/**
1100
 * subscribes a webhook to transaction events for a particular transaction
1101
 *
1102
 * @param identifier    string      the unique identifier of the webhook
1103
 * @param transaction   string      the transaction hash
1104
 * @param confirmations integer     the amount of confirmations to send
1105
 * @param [cb]          function    callback function to call when request is complete
1106
 * @return q.Promise
1107
 */
1108
APIClient.prototype.subscribeTransaction = function(identifier, transaction, confirmations, cb) {
1109
    var self = this;
1110
    var postData = {
1111
        'event_type': 'transaction',
1112
        'transaction': transaction,
1113
        'confirmations': confirmations
1114
    };
1115
1116
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1117
};
1118
1119
/**
1120
 * subscribes a webhook to transaction events on a particular address
1121
 *
1122
 * @param identifier    string      the unique identifier of the webhook
1123
 * @param address       string      the address hash
1124
 * @param confirmations integer     the amount of confirmations to send
1125
 * @param [cb]          function    callback function to call when request is complete
1126
 * @return q.Promise
1127
 */
1128
APIClient.prototype.subscribeAddressTransactions = function(identifier, address, confirmations, cb) {
1129
    var self = this;
1130
    var postData = {
1131
        'event_type': 'address-transactions',
1132
        'address': address,
1133
        'confirmations': confirmations
1134
    };
1135
1136
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1137
};
1138
1139
/**
1140
 * batch subscribes a webhook to multiple transaction events
1141
 *
1142
 * @param  identifier   string      the unique identifier of the webhook
1143
 * @param  batchData    array       An array of objects containing batch event data:
1144
 *                                  {address : 'address', confirmations : 'confirmations']
1145
 *                                  where address is the address to subscribe to and confirmations (optional) is the amount of confirmations to send
1146
 * @param [cb]          function    callback function to call when request is complete
1147
 * @return q.Promise
1148
 */
1149
APIClient.prototype.batchSubscribeAddressTransactions = function(identifier, batchData, cb) {
1150
    var self = this;
1151
    batchData.forEach(function(record) {
1152
        record.event_type = 'address-transactions';
1153
    });
1154
1155
    return self.blocktrailClient.post("/webhook/" + identifier + "/events/batch", null, batchData, cb);
1156
};
1157
1158
/**
1159
 * subscribes a webhook to a new block event
1160
 *
1161
 * @param identifier    string      the unique identifier of the webhook
1162
 * @param [cb]          function    callback function to call when request is complete
1163
 * @return q.Promise
1164
 */
1165
APIClient.prototype.subscribeNewBlocks = function(identifier, cb) {
1166
    var self = this;
1167
    var postData = {
1168
        'event_type': 'block'
1169
    };
1170
1171
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1172
};
1173
1174
/**
1175
 * removes an address transaction event subscription from a webhook
1176
 *
1177
 * @param identifier    string      the unique identifier of the webhook
1178
 * @param address       string      the address hash
1179
 * @param [cb]          function    callback function to call when request is complete
1180
 * @return q.Promise
1181
 */
1182
APIClient.prototype.unsubscribeAddressTransactions = function(identifier, address, cb) {
1183
    var self = this;
1184
1185
    return self.blocktrailClient.delete("/webhook/" + identifier + "/address-transactions/" + address, null, null, cb);
1186
};
1187
1188
/**
1189
 * removes an transaction event subscription from a webhook
1190
 *
1191
 * @param identifier    string      the unique identifier of the webhook
1192
 * @param transaction   string      the transaction hash
1193
 * @param [cb]          function    callback function to call when request is complete
1194
 * @return q.Promise
1195
 */
1196
APIClient.prototype.unsubscribeTransaction = function(identifier, transaction, cb) {
1197
    var self = this;
1198
1199
    return self.blocktrailClient.delete("/webhook/" + identifier + "/transaction/" + transaction, null, null, cb);
1200
};
1201
1202
/**
1203
 * removes a block event subscription from a webhook
1204
 *
1205
 * @param identifier    string      the unique identifier of the webhook
1206
 * @param [cb]          function    callback function to call when request is complete
1207
 * @return q.Promise
1208
 */
1209
APIClient.prototype.unsubscribeNewBlocks = function(identifier, cb) {
1210
    var self = this;
1211
1212
    return self.blocktrailClient.delete("/webhook/" + identifier + "/block", null, null, cb);
1213
};
1214
1215
/**
1216
 * initialize an existing wallet
1217
 *
1218
 * Either takes two argument:
1219
 * @param options       object      {}
1220
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1221
 *
1222
 * Or takes three arguments (old, deprecated syntax):
1223
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1224
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1225
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1220. The second definition is ignored.
Loading history...
1226
 *
1227
 * @returns {q.Promise}
1228
 */
1229
APIClient.prototype.initWallet = function(options, cb) {
1230
    var self = this;
1231
1232
    if (typeof options !== "object") {
1233
        // get the old-style arguments
1234
        options = {
1235
            identifier: arguments[0],
1236
            passphrase: arguments[1]
1237
        };
1238
1239
        cb = arguments[2];
1240
    }
1241
1242
    if (options.check_backup_key) {
1243
        if (typeof options.check_backup_key !== "string") {
1244
            throw new Error("Invalid input, must provide the backup key as a string (the xpub)");
1245
        }
1246
    }
1247
1248
    var deferred = q.defer();
1249
    deferred.promise.spreadNodeify(cb);
1250
1251
    var identifier = options.identifier;
1252
1253
    if (!identifier) {
1254
        deferred.reject(new blocktrail.WalletInitError("Identifier is required"));
1255
        return deferred.promise;
1256
    }
1257
1258
    deferred.resolve(self.blocktrailClient.get("/wallet/" + identifier, null, true).then(function(result) {
1259
        var keyIndex = options.keyIndex || result.key_index;
1260
1261
        options.walletVersion = result.wallet_version;
1262
1263
        if (options.check_backup_key) {
1264
            if (options.check_backup_key !== result.backup_public_key[0]) {
1265
                throw new Error("Backup key returned from server didn't match our own copy");
1266
            }
1267
        }
1268
        var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network);
1269
        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1270
            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1271
        });
1272
        var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) {
1273
            return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network);
1274
        });
1275
1276
        // initialize wallet
1277
        var wallet = new Wallet(
1278
            self,
1279
            identifier,
1280
            options.walletVersion,
1281
            result.primary_mnemonic,
1282
            result.encrypted_primary_seed,
1283
            result.encrypted_secret,
1284
            primaryPublicKeys,
1285
            backupPublicKey,
1286
            blocktrailPublicKeys,
1287
            keyIndex,
1288
            result.segwit || 0,
1289
            self.testnet,
1290
            self.regtest,
1291
            result.checksum,
1292
            result.upgrade_key_index,
1293
            options.useCashAddress,
1294
            options.bypassNewAddressCheck
1295
        );
1296
1297
        wallet.recoverySecret = result.recovery_secret;
1298
1299
        if (!options.readOnly) {
1300
            return wallet.unlock(options).then(function() {
1301
                return wallet;
1302
            });
1303
        } else {
1304
            return wallet;
1305
        }
1306
    }));
1307
1308
    return deferred.promise;
1309
};
1310
1311
APIClient.CREATE_WALLET_PROGRESS_START = 0;
1312
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4;
1313
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5;
1314
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY = 6;
1315
APIClient.CREATE_WALLET_PROGRESS_PRIMARY = 10;
1316
APIClient.CREATE_WALLET_PROGRESS_BACKUP = 20;
1317
APIClient.CREATE_WALLET_PROGRESS_SUBMIT = 30;
1318
APIClient.CREATE_WALLET_PROGRESS_INIT = 40;
1319
APIClient.CREATE_WALLET_PROGRESS_DONE = 100;
1320
1321
/**
1322
 * create a new wallet
1323
 *   - will generate a new primary seed and backup seed
1324
 *
1325
 * Either takes two argument:
1326
 * @param options       object      {}
1327
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1328
 *
1329
 * For v1 wallets (explicitly specify options.walletVersion=v1):
1330
 * @param options       object      {}
0 ignored issues
show
Documentation introduced by
The parameter options has already been documented on line 1326. The second definition is ignored.
Loading history...
1331
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1327. The second definition is ignored.
Loading history...
1332
 *
1333
 * Or takes four arguments (old, deprecated syntax):
1334
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1335
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1336
 * @param keyIndex      int         override for the blocktrail cosign key to use (for development purposes)
0 ignored issues
show
Documentation introduced by
The parameter keyIndex does not exist. Did you maybe forget to remove this comment?
Loading history...
1337
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1327. The second definition is ignored.
Loading history...
1338
 * @returns {q.Promise}
1339
 */
1340
APIClient.prototype.createNewWallet = function(options, cb) {
1341
    /* jshint -W071, -W074 */
1342
1343
    var self = this;
1344
1345
    if (typeof options !== "object") {
1346
        // get the old-style arguments
1347
        var identifier = arguments[0];
1348
        var passphrase = arguments[1];
1349
        var keyIndex = arguments[2];
1350
        cb = arguments[3];
1351
1352
        // keyIndex is optional
1353
        if (typeof keyIndex === "function") {
1354
            cb = keyIndex;
1355
            keyIndex = null;
1356
        }
1357
1358
        options = {
1359
            identifier: identifier,
1360
            passphrase: passphrase,
1361
            keyIndex: keyIndex
1362
        };
1363
    }
1364
1365
    // default to v3
1366
    options.walletVersion = options.walletVersion || Wallet.WALLET_VERSION_V3;
1367
1368
    var deferred = q.defer();
1369
    deferred.promise.spreadNodeify(cb);
1370
1371
    q.nextTick(function() {
1372
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_START);
1373
1374
        options.keyIndex = options.keyIndex || 0;
1375
        options.passphrase = options.passphrase || options.password;
1376
        delete options.password;
1377
1378
        if (!options.identifier) {
1379
            deferred.reject(new blocktrail.WalletCreateError("Identifier is required"));
1380
            return deferred.promise;
1381
        }
1382
1383
        if (options.walletVersion === Wallet.WALLET_VERSION_V1) {
1384
            self._createNewWalletV1(options)
1385
                .progress(function(p) { deferred.notify(p); })
1386
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1387
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1388
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V2) {
1389
            self._createNewWalletV2(options)
1390
                .progress(function(p) { deferred.notify(p); })
1391
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1392
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1393
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V3) {
1394
            self._createNewWalletV3(options)
1395
                .progress(function(p) { deferred.notify(p); })
1396
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1397
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1398
        } else {
1399
            deferred.reject(new blocktrail.WalletCreateError("Invalid wallet version!"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1400
        }
1401
    });
1402
1403
    return deferred.promise;
1404
};
1405
1406
APIClient.prototype._createNewWalletV1 = function(options) {
1407
    var self = this;
1408
1409
    var deferred = q.defer();
1410
1411
    q.nextTick(function() {
1412
1413
        if (!options.primaryMnemonic && !options.primarySeed) {
1414
            if (!options.passphrase && !options.password) {
1415
                deferred.reject(new blocktrail.WalletCreateError("Can't generate Primary Mnemonic without a passphrase"));
1416
                return deferred.promise;
1417
            } else {
1418
                options.primaryMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1419
                if (options.storePrimaryMnemonic !== false) {
1420
                    options.storePrimaryMnemonic = true;
1421
                }
1422
            }
1423
        }
1424
1425
        if (!options.backupMnemonic && !options.backupPublicKey) {
1426
            options.backupMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1427
        }
1428
1429
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
1430
1431
        self.resolvePrimaryPrivateKeyFromOptions(options)
1432
            .then(function(options) {
1433
                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
1434
1435
                return self.resolveBackupPublicKeyFromOptions(options)
1436
                    .then(function(options) {
1437
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
1438
1439
                        // create a checksum of our private key which we'll later use to verify we used the right password
1440
                        var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1441
                        var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1442
                        var keyIndex = options.keyIndex;
1443
1444
                        var primaryPublicKey = options.primaryPrivateKey.deriveHardened(keyIndex).neutered();
1445
1446
                        // send the public keys to the server to store them
1447
                        //  and the mnemonic, which is safe because it's useless without the password
1448
                        return self.storeNewWalletV1(
1449
                            options.identifier,
1450
                            [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1451
                            [options.backupPublicKey.toBase58(), "M"],
1452
                            options.storePrimaryMnemonic ? options.primaryMnemonic : false,
1453
                            checksum,
1454
                            keyIndex,
1455
                            options.segwit || null
1456
                        )
1457
                            .then(function(result) {
1458
                                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1459
1460
                                var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1461
                                    return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1462
                                });
1463
1464
                                var wallet = new Wallet(
1465
                                    self,
1466
                                    options.identifier,
1467
                                    Wallet.WALLET_VERSION_V1,
1468
                                    options.primaryMnemonic,
1469
                                    null,
1470
                                    null,
1471
                                    {keyIndex: primaryPublicKey},
1472
                                    options.backupPublicKey,
1473
                                    blocktrailPublicKeys,
1474
                                    keyIndex,
1475
                                    result.segwit || 0,
1476
                                    self.testnet,
1477
                                    self.regtest,
1478
                                    checksum,
1479
                                    result.upgrade_key_index,
1480
                                    options.useCashAddress,
1481
                                    options.bypassNewAddressCheck
1482
                                );
1483
1484
                                return wallet.unlock({
1485
                                    walletVersion: Wallet.WALLET_VERSION_V1,
1486
                                    passphrase: options.passphrase,
1487
                                    primarySeed: options.primarySeed,
1488
                                    primaryMnemonic: null // explicit null
1489
                                }).then(function() {
1490
                                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1491
                                    return [
1492
                                        wallet,
1493
                                        {
1494
                                            walletVersion: wallet.walletVersion,
1495
                                            primaryMnemonic: options.primaryMnemonic,
1496
                                            backupMnemonic: options.backupMnemonic,
1497
                                            blocktrailPublicKeys: blocktrailPublicKeys
1498
                                        }
1499
                                    ];
1500
                                });
1501
                            });
1502
                    }
1503
                );
1504
            })
1505
            .then(
1506
            function(r) {
1507
                deferred.resolve(r);
1508
            },
1509
            function(e) {
1510
                deferred.reject(e);
1511
            }
1512
        )
1513
        ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1514
    });
1515
1516
    return deferred.promise;
1517
};
1518
1519 View Code Duplication
APIClient.prototype._createNewWalletV2 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1520
    var self = this;
1521
1522
    var deferred = q.defer();
1523
1524
    // avoid modifying passed options
1525
    options = _.merge({}, options);
1526
1527
    determineDataStorageV2_3(options)
1528
        .then(function(options) {
1529
            options.passphrase = options.passphrase || options.password;
1530
            delete options.password;
1531
1532
            // avoid deprecated options
1533
            if (options.primaryPrivateKey) {
1534
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1535
            }
1536
1537
            // seed should be provided or generated
1538
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1539
1540
            return options;
1541
        })
1542
        .then(function(options) {
1543
            return produceEncryptedDataV2(options, deferred.notify.bind(deferred));
1544
        })
1545
        .then(function(options) {
1546
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1547
        })
1548
        .then(function(options) {
1549
            // create a checksum of our private key which we'll later use to verify we used the right password
1550
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1551
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1552
            var keyIndex = options.keyIndex;
1553
1554
            // send the public keys and encrypted data to server
1555
            return self.storeNewWalletV2(
1556
                options.identifier,
1557
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1558
                [options.backupPublicKey.toBase58(), "M"],
1559
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1560
                options.storeDataOnServer ? options.encryptedSecret : false,
1561
                options.storeDataOnServer ? options.recoverySecret : false,
1562
                checksum,
1563
                keyIndex,
1564
                options.support_secret || null,
1565
                options.segwit || null
1566
            )
1567
                .then(
1568
                function(result) {
1569
                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1570
1571
                    var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1572
                        return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1573
                    });
1574
1575
                    var wallet = new Wallet(
1576
                        self,
1577
                        options.identifier,
1578
                        Wallet.WALLET_VERSION_V2,
1579
                        null,
1580
                        options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1581
                        options.storeDataOnServer ? options.encryptedSecret : null,
1582
                        {keyIndex: options.primaryPublicKey},
1583
                        options.backupPublicKey,
1584
                        blocktrailPublicKeys,
1585
                        keyIndex,
1586
                        result.segwit || 0,
1587
                        self.testnet,
1588
                        self.regtest,
1589
                        checksum,
1590
                        result.upgrade_key_index,
1591
                        options.useCashAddress,
1592
                        options.bypassNewAddressCheck
1593
                    );
1594
1595
                    // pass along decrypted data to avoid extra work
1596
                    return wallet.unlock({
1597
                        walletVersion: Wallet.WALLET_VERSION_V2,
1598
                        passphrase: options.passphrase,
1599
                        primarySeed: options.primarySeed,
1600
                        secret: options.secret
1601
                    }).then(function() {
1602
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1603
                        return [
1604
                            wallet,
1605
                            {
1606
                                walletVersion: wallet.walletVersion,
1607
                                encryptedPrimarySeed: options.encryptedPrimarySeed ?
1608
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedPrimarySeed, 'base64', 'hex')) :
1609
                                    null,
1610
                                backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed.toString('hex')) : null,
1611
                                recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1612
                                    bip39.entropyToMnemonic(blocktrail.convert(options.recoveryEncryptedSecret, 'base64', 'hex')) :
1613
                                    null,
1614
                                encryptedSecret: options.encryptedSecret ?
1615
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedSecret, 'base64', 'hex')) :
1616
                                    null,
1617
                                blocktrailPublicKeys: blocktrailPublicKeys
1618
                            }
1619
                        ];
1620
                    });
1621
                }
1622
            );
1623
        })
1624
       .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1625
1626
    return deferred.promise;
1627
};
1628
1629 View Code Duplication
APIClient.prototype._createNewWalletV3 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1630
    var self = this;
1631
1632
    var deferred = q.defer();
1633
1634
    // avoid modifying passed options
1635
    options = _.merge({}, options);
1636
1637
    determineDataStorageV2_3(options)
1638
        .then(function(options) {
1639
            options.passphrase = options.passphrase || options.password;
1640
            delete options.password;
1641
1642
            // avoid deprecated options
1643
            if (options.primaryPrivateKey) {
1644
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1645
            }
1646
1647
            // seed should be provided or generated
1648
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1649
1650
            return options;
1651
        })
1652
        .then(function(options) {
1653
            return self.produceEncryptedDataV3(options, deferred.notify.bind(deferred));
1654
        })
1655
        .then(function(options) {
1656
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1657
        })
1658
        .then(function(options) {
1659
            // create a checksum of our private key which we'll later use to verify we used the right password
1660
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1661
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1662
            var keyIndex = options.keyIndex;
1663
1664
            // send the public keys and encrypted data to server
1665
            return self.storeNewWalletV3(
1666
                options.identifier,
1667
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1668
                [options.backupPublicKey.toBase58(), "M"],
1669
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1670
                options.storeDataOnServer ? options.encryptedSecret : false,
1671
                options.storeDataOnServer ? options.recoverySecret : false,
1672
                checksum,
1673
                keyIndex,
1674
                options.support_secret || null,
1675
                options.segwit || null
1676
            )
1677
                .then(
1678
                    // result, deferred, self(apiclient)
1679
                    function(result) {
1680
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1681
1682
                        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1683
                            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1684
                        });
1685
1686
                        var wallet = new Wallet(
1687
                            self,
1688
                            options.identifier,
1689
                            Wallet.WALLET_VERSION_V3,
1690
                            null,
1691
                            options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1692
                            options.storeDataOnServer ? options.encryptedSecret : null,
1693
                            {keyIndex: options.primaryPublicKey},
1694
                            options.backupPublicKey,
1695
                            blocktrailPublicKeys,
1696
                            keyIndex,
1697
                            result.segwit || 0,
1698
                            self.testnet,
1699
                            self.regtest,
1700
                            checksum,
1701
                            result.upgrade_key_index,
1702
                            options.useCashAddress,
1703
                            options.bypassNewAddressCheck
1704
                        );
1705
1706
                        // pass along decrypted data to avoid extra work
1707
                        return wallet.unlock({
1708
                            walletVersion: Wallet.WALLET_VERSION_V3,
1709
                            passphrase: options.passphrase,
1710
                            primarySeed: options.primarySeed,
1711
                            secret: options.secret
1712
                        }).then(function() {
1713
                            deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1714
                            return [
1715
                                wallet,
1716
                                {
1717
                                    walletVersion: wallet.walletVersion,
1718
                                    encryptedPrimarySeed: options.encryptedPrimarySeed ? EncryptionMnemonic.encode(options.encryptedPrimarySeed) : null,
1719
                                    backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed) : null,
1720
                                    recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1721
                                        EncryptionMnemonic.encode(options.recoveryEncryptedSecret) : null,
1722
                                    encryptedSecret: options.encryptedSecret ? EncryptionMnemonic.encode(options.encryptedSecret) : null,
1723
                                    blocktrailPublicKeys: blocktrailPublicKeys
1724
                                }
1725
                            ];
1726
                        });
1727
                    }
1728
                );
1729
        })
1730
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1731
1732
    return deferred.promise;
1733
};
1734
1735
function verifyPublicBip32Key(bip32Key, network) {
1736
    var hk = bitcoin.HDNode.fromBase58(bip32Key[0], network);
1737
    if (typeof hk.keyPair.d !== "undefined") {
1738
        throw new Error('BIP32Key contained private key material - abort');
1739
    }
1740
1741
    if (bip32Key[1].slice(0, 1) !== "M") {
1742
        throw new Error("BIP32Key contained non-public path - abort");
1743
    }
1744
}
1745
1746
function verifyPublicOnly(walletData, network) {
1747
    verifyPublicBip32Key(walletData.primary_public_key, network);
1748
    verifyPublicBip32Key(walletData.backup_public_key, network);
1749
}
1750
1751
/**
1752
 * create wallet using the API
1753
 *
1754
 * @param identifier            string      the wallet identifier to create
1755
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1756
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1757
 * @param primaryMnemonic       string      mnemonic to store
1758
 * @param checksum              string      checksum to store
1759
 * @param keyIndex              int         keyIndex that was used to create wallet
1760
 * @param segwit                bool
1761
 * @returns {q.Promise}
1762
 */
1763
APIClient.prototype.storeNewWalletV1 = function(identifier, primaryPublicKey, backupPublicKey, primaryMnemonic,
1764
                                                checksum, keyIndex, segwit) {
1765
    var self = this;
1766
1767
    var postData = {
1768
        identifier: identifier,
1769
        wallet_version: Wallet.WALLET_VERSION_V1,
1770
        primary_public_key: primaryPublicKey,
1771
        backup_public_key: backupPublicKey,
1772
        primary_mnemonic: primaryMnemonic,
1773
        checksum: checksum,
1774
        key_index: keyIndex,
1775
        segwit: segwit
1776
    };
1777
1778
    verifyPublicOnly(postData, self.network);
1779
1780
    return self.blocktrailClient.post("/wallet", null, postData);
1781
};
1782
1783
/**
1784
 * create wallet using the API
1785
 *
1786
 * @param identifier            string      the wallet identifier to create
1787
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1788
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1789
 * @param encryptedPrimarySeed  string      openssl format
1790
 * @param encryptedSecret       string      openssl format
1791
 * @param recoverySecret        string      openssl format
1792
 * @param checksum              string      checksum to store
1793
 * @param keyIndex              int         keyIndex that was used to create wallet
1794
 * @param supportSecret         string
1795
 * @param segwit                bool
1796
 * @returns {q.Promise}
1797
 */
1798
APIClient.prototype.storeNewWalletV2 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1799
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1800
    var self = this;
1801
1802
    var postData = {
1803
        identifier: identifier,
1804
        wallet_version: Wallet.WALLET_VERSION_V2,
1805
        primary_public_key: primaryPublicKey,
1806
        backup_public_key: backupPublicKey,
1807
        encrypted_primary_seed: encryptedPrimarySeed,
1808
        encrypted_secret: encryptedSecret,
1809
        recovery_secret: recoverySecret,
1810
        checksum: checksum,
1811
        key_index: keyIndex,
1812
        support_secret: supportSecret || null,
1813
        segwit: segwit
1814
    };
1815
1816
    verifyPublicOnly(postData, self.network);
1817
1818
    return self.blocktrailClient.post("/wallet", null, postData);
1819
};
1820
1821
/**
1822
 * create wallet using the API
1823
 *
1824
 * @param identifier            string      the wallet identifier to create
1825
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1826
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1827
 * @param encryptedPrimarySeed  Buffer      buffer of ciphertext
1828
 * @param encryptedSecret       Buffer      buffer of ciphertext
1829
 * @param recoverySecret        Buffer      buffer of recovery secret
1830
 * @param checksum              string      checksum to store
1831
 * @param keyIndex              int         keyIndex that was used to create wallet
1832
 * @param supportSecret         string
1833
 * @param segwit                bool
1834
 * @returns {q.Promise}
1835
 */
1836
APIClient.prototype.storeNewWalletV3 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1837
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1838
    var self = this;
1839
1840
    var postData = {
1841
        identifier: identifier,
1842
        wallet_version: Wallet.WALLET_VERSION_V3,
1843
        primary_public_key: primaryPublicKey,
1844
        backup_public_key: backupPublicKey,
1845
        encrypted_primary_seed: encryptedPrimarySeed.toString('base64'),
1846
        encrypted_secret: encryptedSecret.toString('base64'),
1847
        recovery_secret: recoverySecret.toString('hex'),
1848
        checksum: checksum,
1849
        key_index: keyIndex,
1850
        support_secret: supportSecret || null,
1851
        segwit: segwit
1852
    };
1853
1854
    verifyPublicOnly(postData, self.network);
1855
1856
    return self.blocktrailClient.post("/wallet", null, postData);
1857
};
1858
1859
/**
1860
 * create wallet using the API
1861
 *
1862
 * @param identifier            string      the wallet identifier to create
1863
 * @param postData              object
1864
 * @param [cb]                  function    callback(err, result)
1865
 * @returns {q.Promise}
1866
 */
1867
APIClient.prototype.updateWallet = function(identifier, postData, cb) {
1868
    var self = this;
1869
1870
    return self.blocktrailClient.post("/wallet/" + identifier, null, postData, cb);
1871
};
1872
1873
/**
1874
 * upgrade wallet to use a new account number
1875
 *  the account number specifies which blocktrail cosigning key is used
1876
 *
1877
 * @param identifier            string      the wallet identifier
1878
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1879
 * @param keyIndex              int         keyIndex that was used to create wallet
1880
 * @param [cb]                  function    callback(err, result)
1881
 * @returns {q.Promise}
1882
 */
1883
APIClient.prototype.upgradeKeyIndex = function(identifier, keyIndex, primaryPublicKey, cb) {
1884
    var self = this;
1885
1886
    return self.blocktrailClient.post("/wallet/" + identifier + "/upgrade", null, {
1887
        key_index: keyIndex,
1888
        primary_public_key: primaryPublicKey
1889
    }, cb);
1890
};
1891
1892
/**
1893
 * get the balance for the wallet
1894
 *
1895
 * @param identifier            string      the wallet identifier
1896
 * @param [cb]                  function    callback(err, result)
1897
 * @returns {q.Promise}
1898
 */
1899
APIClient.prototype.getWalletBalance = function(identifier, cb) {
1900
    var self = this;
1901
1902
    return self.blocktrailClient.get("/wallet/" + identifier + "/balance", null, true, cb);
1903
};
1904
1905
/**
1906
 * do HD wallet discovery for the wallet
1907
 *
1908
 * @param identifier            string      the wallet identifier
1909
 * @param [cb]                  function    callback(err, result)
1910
 * @returns {q.Promise}
1911
 */
1912
APIClient.prototype.doWalletDiscovery = function(identifier, gap, cb) {
1913
    var self = this;
1914
1915
    return self.blocktrailClient.get("/wallet/" + identifier + "/discovery", {gap: gap}, true, cb);
1916
};
1917
1918
1919
/**
1920
 * get a new derivation number for specified parent path
1921
 *  eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2
1922
 *
1923
 * @param identifier            string      the wallet identifier
1924
 * @param path                  string      the parent path for which to get a new derivation,
1925
 *                                           can be suffixed with /* to make it clear on which level the derivations hould be
1926
 * @param [cb]                  function    callback(err, result)
1927
 * @returns {q.Promise}
1928
 */
1929
APIClient.prototype.getNewDerivation = function(identifier, path, cb) {
1930
    var self = this;
1931
1932
    return self.blocktrailClient.post("/wallet/" + identifier + "/path", null, {path: path}, cb);
1933
};
1934
1935
1936
/**
1937
 * delete the wallet
1938
 *  the checksum address and a signature to verify you ownership of the key of that checksum address
1939
 *  is required to be able to delete a wallet
1940
 *
1941
 * @param identifier            string      the wallet identifier
1942
 * @param checksumAddress       string      the address for your master private key (and the checksum used when creating the wallet)
1943
 * @param checksumSignature     string      a signature of the checksum address as message signed by the private key matching that address
1944
 * @param [force]               bool        ignore warnings (such as a non-zero balance)
1945
 * @param [cb]                  function    callback(err, result)
1946
 * @returns {q.Promise}
1947
 */
1948
APIClient.prototype.deleteWallet = function(identifier, checksumAddress, checksumSignature, force, cb) {
1949
    var self = this;
1950
1951
    if (typeof force === "function") {
1952
        cb = force;
1953
        force = false;
1954
    }
1955
1956
    return self.blocktrailClient.delete("/wallet/" + identifier, {force: force}, {
1957
        checksum: checksumAddress,
1958
        signature: checksumSignature
1959
    }, cb);
1960
};
1961
1962
/**
1963
 * use the API to get the best inputs to use based on the outputs
1964
 *
1965
 * the return array has the following format:
1966
 * [
1967
 *  "utxos" => [
1968
 *      [
1969
 *          "hash" => "<txHash>",
1970
 *          "idx" => "<index of the output of that <txHash>",
1971
 *          "scriptpubkey_hex" => "<scriptPubKey-hex>",
1972
 *          "value" => 32746327,
1973
 *          "address" => "1address",
1974
 *          "path" => "m/44'/1'/0'/0/13",
1975
 *          "redeem_script" => "<redeemScript-hex>",
1976
 *      ],
1977
 *  ],
1978
 *  "fee"   => 10000,
1979
 *  "change"=> 1010109201,
1980
 * ]
1981
 *
1982
 * @param identifier        string      the wallet identifier
1983
 * @param pay               array       {'address': (int)value}     coins to send
1984
 * @param lockUTXO          bool        lock UTXOs for a few seconds to allow for transaction to be created
1985
 * @param allowZeroConf     bool        allow zero confirmation unspent outputs to be used in coin selection
1986
 * @param feeStrategy       string      defaults to
1987
 * @param options
1988
 * @param [cb]              function    callback(err, utxos, fee, change)
1989
 * @returns {q.Promise}
1990
 */
1991
APIClient.prototype.coinSelection = function(identifier, pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1992
    var self = this;
1993
1994
    if (typeof feeStrategy === "function") {
1995
        cb = feeStrategy;
1996
        feeStrategy = null;
1997
        options = {};
1998
    } else if (typeof options === "function") {
1999
        cb = options;
2000
        options = {};
2001
    }
2002
2003
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
2004
    options = options || {};
2005
2006
    var deferred = q.defer();
2007
    deferred.promise.spreadNodeify(cb);
2008
2009
    var params = {
2010
        lock: lockUTXO,
2011
        zeroconf: allowZeroConf ? 1 : 0,
2012
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
2013
        fee_strategy: feeStrategy
2014
    };
2015
2016
    if (options.forcefee) {
2017
        params['forcefee'] = options.forcefee;
2018
    }
2019
2020
    deferred.resolve(
2021
        self.blocktrailClient.post("/wallet/" + identifier + "/coin-selection", params, pay).then(
2022
            function(result) {
2023
                return [result.utxos, result.fee, result.change, result];
2024
            },
2025
            function(err) {
2026
                if (err.message.match(/too low to pay the fee/)) {
2027
                    throw blocktrail.WalletFeeError(err);
2028
                }
2029
2030
                throw err;
2031
            }
2032
        )
2033
    );
2034
2035
    return deferred.promise;
2036
};
2037
2038
/**
2039
 * @param [cb]              function    callback(err, utxos, fee, change)
2040
 * @returns {q.Promise}
2041
 */
2042
APIClient.prototype.feePerKB = function(cb) {
2043
    var self = this;
2044
2045
    var deferred = q.defer();
2046
    deferred.promise.spreadNodeify(cb);
2047
2048
    deferred.resolve(self.blocktrailClient.get("/fee-per-kb"));
2049
2050
    return deferred.promise;
2051
};
2052
2053
/**
2054
 * send the transaction using the API
2055
 *
2056
 * @param identifier        string      the wallet identifier
2057
 * @param txHex             string      partially signed transaction as hex string
2058
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
2059
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
2060
 * @param [twoFactorToken]  string      2FA token
2061
 * @param [prioboost]       bool
2062
 * @param [cb]              function    callback(err, txHash)
2063
 * @returns {q.Promise}
2064
 */
2065
APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
2066
    var self = this;
2067
2068
    if (typeof twoFactorToken === "function") {
2069
        cb = twoFactorToken;
2070
        twoFactorToken = null;
2071
        prioboost = false;
2072
    } else if (typeof prioboost === "function") {
2073
        cb = prioboost;
2074
        prioboost = false;
2075
    }
2076
2077
    var data = {
2078
        paths: paths,
2079
        two_factor_token: twoFactorToken
2080
    };
2081
    if (typeof txHex === "string") {
2082
        data.raw_transaction = txHex;
2083
    } else if (typeof txHex === "object") {
2084
        Object.keys(txHex).map(function(key) {
2085
            data[key] = txHex[key];
2086
        });
2087
    }
2088
2089
    return self.blocktrailClient.post(
2090
        "/wallet/" + identifier + "/send",
2091
        {
2092
            check_fee: checkFee ? 1 : 0,
2093
            prioboost: prioboost ? 1 : 0
2094
        },
2095
        data,
2096
        cb
2097
    );
2098
};
2099
2100
/**
2101
 * setup a webhook for this wallet
2102
 *
2103
 * @param identifier        string      the wallet identifier
2104
 * @param webhookIdentifier string      identifier for the webhook
2105
 * @param url               string      URL to receive webhook events
2106
 * @param [cb]              function    callback(err, webhook)
2107
 * @returns {q.Promise}
2108
 */
2109
APIClient.prototype.setupWalletWebhook = function(identifier, webhookIdentifier, url, cb) {
2110
    var self = this;
2111
2112
    return self.blocktrailClient.post("/wallet/" + identifier + "/webhook", null, {url: url, identifier: webhookIdentifier}, cb);
2113
};
2114
2115
/**
2116
 * delete a webhook that was created for this wallet
2117
 *
2118
 * @param identifier        string      the wallet identifier
2119
 * @param webhookIdentifier string      identifier for the webhook
2120
 * @param [cb]              function    callback(err, success)
2121
 * @returns {q.Promise}
2122
 */
2123
APIClient.prototype.deleteWalletWebhook = function(identifier, webhookIdentifier, cb) {
2124
    var self = this;
2125
2126
    return self.blocktrailClient.delete("/wallet/" + identifier + "/webhook/" + webhookIdentifier, null, null, cb);
2127
};
2128
2129
/**
2130
 * get all transactions for an wallet (paginated)
2131
 *
2132
 * @param identifier    string      wallet identifier
2133
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2134
 * @param [cb]          function    callback function to call when request is complete
2135
 * @return q.Promise
2136
 */
2137
APIClient.prototype.walletTransactions = function(identifier, params, cb) {
2138
    var self = this;
2139
2140
    if (typeof params === "function") {
2141
        cb = params;
2142
        params = null;
2143
    }
2144
2145
    return self.blocktrailClient.get("/wallet/" + identifier + "/transactions", params, true, cb);
2146
};
2147
2148
/**
2149
 * get all addresses for an wallet (paginated)
2150
 *
2151
 * @param identifier    string      wallet identifier
2152
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2153
 * @param [cb]          function    callback function to call when request is complete
2154
 * @return q.Promise
2155
 */
2156
APIClient.prototype.walletAddresses = function(identifier, params, cb) {
2157
    var self = this;
2158
2159
    if (typeof params === "function") {
2160
        cb = params;
2161
        params = null;
2162
    }
2163
2164
    return self.blocktrailClient.get("/wallet/" + identifier + "/addresses", params, true, cb);
2165
};
2166
2167
/**
2168
 * @param identifier    string      wallet identifier
2169
 * @param address       string      the address to label
2170
 * @param label         string      the label
2171
 * @param [cb]          function    callback(err, res)
2172
 * @return q.Promise
2173
 */
2174
APIClient.prototype.labelWalletAddress = function(identifier, address, label, cb) {
2175
    var self = this;
2176
2177
    return self.blocktrailClient.post("/wallet/" + identifier + "/address/" + address + "/label", null, {label: label}, cb);
2178
};
2179
2180
APIClient.prototype.walletMaxSpendable = function(identifier, allowZeroConf, feeStrategy, options, cb) {
2181
    var self = this;
2182
2183
    if (typeof feeStrategy === "function") {
2184
        cb = feeStrategy;
2185
        feeStrategy = null;
2186
    } else if (typeof options === "function") {
2187
        cb = options;
2188
        options = {};
2189
    }
2190
2191
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
2192
    options = options || {};
2193
2194
    var params = {
2195
        outputs: options.outputs ? options.outputs : 1,
2196
        zeroconf: allowZeroConf ? 1 : 0,
2197
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
2198
        fee_strategy: feeStrategy
2199
    };
2200
2201
    if (options.forcefee) {
2202
        params['forcefee'] = options.forcefee;
2203
    }
2204
2205
    return self.blocktrailClient.get("/wallet/" + identifier + "/max-spendable", params, true, cb);
2206
};
2207
2208
/**
2209
 * get all UTXOs for an wallet (paginated)
2210
 *
2211
 * @param identifier    string      wallet identifier
2212
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2213
 * @param [cb]          function    callback function to call when request is complete
2214
 * @return q.Promise
2215
 */
2216
APIClient.prototype.walletUTXOs = function(identifier, params, cb) {
2217
    var self = this;
2218
2219
    if (typeof params === "function") {
2220
        cb = params;
2221
        params = null;
2222
    }
2223
2224
    return self.blocktrailClient.get("/wallet/" + identifier + "/utxos", params, true, cb);
2225
};
2226
2227
/**
2228
 * get a paginated list of all wallets associated with the api user
2229
 *
2230
 * @param [params]      object      pagination: {page: 1, limit: 20}
2231
 * @param [cb]          function    callback function to call when request is complete
2232
 * @return q.Promise
2233
 */
2234
APIClient.prototype.allWallets = function(params, cb) {
2235
    var self = this;
2236
2237
    if (typeof params === "function") {
2238
        cb = params;
2239
        params = null;
2240
    }
2241
2242
    return self.blocktrailClient.get("/wallets", params, true, cb);
2243
};
2244
2245
/**
2246
 * verify a message signed bitcoin-core style
2247
 *
2248
 * @param message        string
2249
 * @param address        string
2250
 * @param signature      string
2251
 * @param [cb]          function    callback function to call when request is complete
2252
 * @return q.Promise
2253
 */
2254
APIClient.prototype.verifyMessage = function(message, address, signature, cb) {
2255
    var self = this;
2256
2257
    var deferred = q.defer();
2258
    deferred.promise.nodeify(cb);
2259
    try {
2260
        var result = bitcoinMessage.verify(address, self.network.messagePrefix, message, new Buffer(signature, 'base64'));
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2261
        deferred.resolve(result);
2262
    } catch (e) {
2263
        deferred.reject(e);
2264
    }
2265
2266
    return deferred.promise;
2267
};
2268
2269
/**
2270
 * max is 0.001
2271
 * testnet only
2272
 *
2273
 * @param address
2274
 * @param amount
2275
 * @param cb
2276
 */
2277
APIClient.prototype.faucetWithdrawl = function(address, amount, cb) {
2278
    var self = this;
2279
2280
    return self.blocktrailClient.post("/faucet/withdrawl", null, {address: address, amount: amount}, cb);
2281
};
2282
2283
/**
2284
 * send a raw transaction
2285
 *
2286
 * @param rawTransaction    string      raw transaction as HEX
2287
 * @param [cb]              function    callback function to call when request is complete
2288
 * @return q.Promise
2289
 */
2290
APIClient.prototype.sendRawTransaction = function(rawTransaction, cb) {
2291
    var self = this;
2292
2293
    return self.blocktrailClient.post("/send-raw-tx", null, rawTransaction, cb);
2294
};
2295
2296
/**
2297
 * get the current price index
2298
 *
2299
 * @param [cb]          function    callback({'USD': 287.30})
2300
 * @return q.Promise
2301
 */
2302
APIClient.prototype.price = function(cb) {
2303
    var self = this;
2304
2305
    return self.blocktrailClient.get("/price", null, false, cb);
2306
};
2307
2308
module.exports = APIClient;
2309